package com.ruoyi.hiscomm.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
import co.elastic.clients.elasticsearch.indices.PutMappingRequest;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.elasticsearch.model.EsPageResult;
import com.ruoyi.common.elasticsearch.service.ElasticSearchService;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.hiscomm.domain.DeptDictDo;
import com.ruoyi.hiscomm.domain.DrugBasicInfoDo;
import com.ruoyi.hiscomm.mapper.DrugBasicInfoDoMapper;
import com.ruoyi.hiscomm.request.DeptParm;
import com.ruoyi.hiscomm.request.DrugParm;
import com.ruoyi.hiscomm.service.IDrugBasicInfoService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RList;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.ruoyi.common.core.utils.ListPaginator.getTotalPages;
import static com.ruoyi.common.core.utils.ListPaginator.paginate;

/**
 * @ClassName DrugBasicInfoServiceImpl
 * @Author samula
 * @Descripe product by com.doctcloud
 * @Data 2025-09-28 4:59
 **/
@Service
@Slf4j
public class DrugBasicInfoServiceImpl extends ServiceImpl<DrugBasicInfoDoMapper,DrugBasicInfoDo> implements IDrugBasicInfoService {
    /**
     * 缓存记录
     */
    private static final String RS_INDEX_NAME = "his_dict:drug_basic_info";
    /**
     * Redis临时的HASHKEY
     */
    private static final String RS_HASH_KEY = "his_dict:druginfo_hash";
    /**
     * Es索引必须全部是小写
     */
    private static final String ES_INDEX_NAME = "drug_basic_info";
    /**
     * 缓存锁前缀
     */
    private static final String CACHE_LOCK_PREFIX = "his_dict:lock:";
    /**
     * 同步锁前缀
     */
    private static final String SYNC_LOCK_PREFIX = "his_dict:sync:";
    /**
     * 缓存过期时间(小时)
     */
    private static final long CACHE_EXPIRE_HOURS = 24;
    /**
     * 锁等待时间(秒)
     */
    private static final long LOCK_WAIT_SECONDS = 5;
    /**
     * 锁持有时间(秒)
     */
    private static final long LOCK_HOLD_SECONDS = 10;
    /**
     * 同步锁持有时间(秒)
     */
    private static final long SYNC_LOCK_HOLD_SECONDS = 30;
    /**
     * 缓存等待重试次数
     */
    private static final int CACHE_WAIT_RETRIES = 30;
    /**
     * 缓存等待间隔(毫秒)
     */
    private static final long CACHE_WAIT_INTERVAL_MS = 100;

    @Resource
    private ElasticSearchService elasticSearchService;
    @Resource
    private RedisService redisService;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private ElasticsearchClient esClient;

    /**
     * 初始化同步科室数据
     */
    @PostConstruct
    public void ini() throws IOException {
        // 获取全局同步锁
        RLock syncLock = redissonClient.getLock(SYNC_LOCK_PREFIX + RS_INDEX_NAME);
        boolean isSyncLocked = false;

        try {
            // 尝试获取锁
            isSyncLocked = syncLock.tryLock(LOCK_WAIT_SECONDS, SYNC_LOCK_HOLD_SECONDS, TimeUnit.SECONDS);
            if (!isSyncLocked) {
                log.info("已有线程正在执行 {} 同步操作，当前线程退出", RS_INDEX_NAME);
                return;
            }

            // 1. 清理相关缓存和索引
            elasticSearchService.deleteIndex(ES_INDEX_NAME);
            clearDeptCache();

            // 2. 初始化索引和映射
            initIndexAndMapping();
            log.info("开始从数据库同步科室数据到Elasticsearch...");

            // 3. 从数据库查询数据
            List<DrugBasicInfoDo> drugBasicInfoList = listDrugBasicInfos();
            if (drugBasicInfoList == null || drugBasicInfoList.isEmpty()) {
                log.info("数据库中没有科室数据，同步结束。");
                return;
            }
            // 4. 同步数据到Elasticsearch
            Map<String, DrugBasicInfoDo> drugMap = new HashMap<>(drugBasicInfoList.size());
            for (DrugBasicInfoDo drug : drugBasicInfoList) {
                drugMap.put(drug.getDrugStdCode(), drug); // key为文档ID，value为数据对象
            }
            elasticSearchService.bulkSave(ES_INDEX_NAME, drugMap); // 调用批量保存方法

            // 5. 重新加载缓存
            loadDrugCache(drugBasicInfoList);

            log.info("科室数据同步完成，共 [{}] 条。", drugBasicInfoList.size());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("同步过程被中断", e);
        } finally {
            // 确保锁释放
            releaseLock(syncLock, isSyncLocked);
        }
    }


    @Override
    public IPage<DrugBasicInfoDo> pageESDrugList(DrugParm drug) {
        try {
            initIndexAndMapping();

            long current = ObjectUtils.isEmpty(drug.getCurrentPage()) ? 1 : drug.getCurrentPage();
            long size = ObjectUtils.isEmpty(drug.getPageSize()) ? 10 : drug.getPageSize();
            // 无搜索词时返回空分页
            if ( StringUtils.isEmpty(drug.getDrugName())) {
                return new Page<>(current, size, 0);
            }

            // 使用ES进行分页模糊查询
            List<String> searchFields = Arrays.asList("drugName", "drugStdCode", "drugCode","firmId");
            EsPageResult<DrugBasicInfoDo> esResult = elasticSearchService.fuzzyMultiSearchWithTotal(
                    ES_INDEX_NAME, searchFields, drug.getDrugName(), (int) current, (int) size, DrugBasicInfoDo.class);

            // 构建分页结果
            IPage<DrugBasicInfoDo> resultPage = new Page<>(current, size);
            resultPage.setRecords(esResult.getRecords());
            resultPage.setTotal(esResult.getTotal()); // TODO: 需改造ES服务层支持总数查询

            return resultPage;

        } catch (IOException e) {
            log.error("ES分页查询失败", e);
            return new Page<>();
        }
    }

    @Override
    public IPage<DrugBasicInfoDo> pageRedisDeptList(DrugParm drug) {
        List<DrugBasicInfoDo> allDrugs = listDrugBasicInfos();
        if (drug == null) {
            drug = new DrugParm();
        }
        // 处理分页参数默认值（已确保非null）
        int currentPage = drug.getCurrentPage() == null || drug.getCurrentPage() < 1
                ? 1
                : drug.getCurrentPage();
        int pageSize = drug.getPageSize() == null || drug.getPageSize() < 1
                ? 10
                : drug.getPageSize();
        // 限制最大页大小，避免一次性查询过多数据
        pageSize = Math.min(pageSize, 100);
        IPage<DrugBasicInfoDo> resultPage = new Page<>(currentPage, pageSize);
        String drugName = drug.getDrugName();
        if (StringUtils.isEmpty(drugName)) {
            allDrugs = allDrugs == null ? new ArrayList<>() : allDrugs; // 防止null
            int total = allDrugs.size();
            resultPage.setTotal(total);
            resultPage.setPages(getTotalPages(allDrugs, pageSize)); // 使用处理后的pageSize
            resultPage.setRecords(paginate(allDrugs, currentPage, pageSize)); // 使用处理后的参数
        } else {
            allDrugs = allDrugs.stream()
                    .filter(item -> item.getDrugName().contains(drugName))
                    .collect(Collectors.toList());
            allDrugs = allDrugs == null ? new ArrayList<>() : allDrugs; // 防止null
            int total = allDrugs.size();
            resultPage.setTotal(total);
            resultPage.setPages(getTotalPages(allDrugs, pageSize)); // 使用处理后的pageSize
            resultPage.setRecords(paginate(allDrugs, currentPage, pageSize)); // 使用处理后的参数
        }
        return resultPage;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertDrugBasicInfo(DrugBasicInfoDo drug) throws IOException {
        // 参数校验
        String s = checkInputDrugInfoValid(drug);
        if (StringUtils.isNotEmpty(s)){
            log.error(s);
            return 0;
        }
        // 检查药品的唯一性
        if (checkDrugUnique(drug)) {
            log.warn("药品名称 [{},{},{},{}] 已存在，添加失败", drug.getDrugName(),drug.getDrugSpec(),drug.getFirmId(),drug.getDrugUnit());
            return 0;
        }

        // 确保索引和映射存在
        initIndexAndMapping();

        // 生成ID和创建时间
        drug.setCreateDate(new Date());

        // 1. 先保存到数据库
        int insertCount = baseMapper.insertDrugBasicInfo(drug);
        if (insertCount > 0) {
            // 2. 再保存到Elasticsearch
            elasticSearchService.save(ES_INDEX_NAME, drug.getDrugStdCode(), drug);

            // 3. 直接同步更新Redis缓存 (核心改动)
            syncDrugToRedis(drug);

            log.info("药品 [{}] 已成功添加到数据库、Elasticsearch和Redis", drug.getDrugName());
        }
        return insertCount;
    }

    @Override
    public int updateDrugBasicInfo(DrugBasicInfoDo drug) throws IOException {
        // 参数校验
        if (drug == null) {
            log.error("药品不能为空，更新失败");
            return 0;
        }
        // 参数校验
        String s = checkInputDrugInfoValid(drug);
        if (StringUtils.isNotEmpty(s)){
            log.error(s);
            return 0;
        }
        // 确保索引和映射存在
        initIndexAndMapping();
        // 1. 先更新数据库
        drug.setUpdateDate(new Date());
        int updateCount = baseMapper.updateDrugBasicInfo(drug);
        if (updateCount > 0) {
            // 2. 再更新到Elasticsearch
            elasticSearchService.save(ES_INDEX_NAME, drug.getDrugStdCode(), drug);

            // 3. 直接同步更新Redis缓存 (核心改动)
            syncDrugToRedis(drug);

            log.info("药品 [{}] 已同步更新到数据库、Elasticsearch和Redis", drug.getDrugName());
        }
        return updateCount;
    }

    @Override
    public int deleteDrugBasicInfo(DrugBasicInfoDo drug) {
        if (ObjectUtils.isEmpty(drug)){
            log.error("无效数据删除");
            return 0;
        }
        if (StringUtils.isEmpty(drug.getDrugStdCode())) {
            log.error("删除药品的std_code不能为空");
            return 0;
        }

        // 1. 先从数据库删除
        int deleteCount = baseMapper.deleteDrugBasicInfo(drug.getDrugStdCode());
        if (deleteCount > 0) {
            // 2. 再从Elasticsearch删除
            elasticSearchService.delete(ES_INDEX_NAME, drug.getDrugStdCode());

            // 3. 直接从Redis缓存中删除 (保持一致性)
            removeDrugFromRedis(drug.getDrugStdCode());

            log.info("药品 [StdCode: {}] 已成功从数据库、Elasticsearch和Redis删除", drug.getDrugStdCode());
            return 1;
        }
        return 0;
    }

    @Override
    public DrugBasicInfoDo getOneDrugBasicInfo(DrugBasicInfoDo drug) {
        // 定义缓存过期时间
        final long CACHE_EXPIRE_MINUTES = 10;
        final String RS_HASH_KEY = "drug_basic_info_hash";

        // 场景1：drug对象为空，返回任意一条数据
        if (ObjectUtils.isEmpty(drug)) {
            String cacheKey = "drug:basic:info:any";
            // 先查Redis缓存
            DrugBasicInfoDo drugFromCache = redisService.getCacheObject(cacheKey);
            if (drugFromCache != null) {
                log.info("从Redis缓存获取任意药品数据成功");
                return drugFromCache;
            }

            // 缓存未命中，查Elasticsearch
            try {
                // 构建ES查询（获取任意一条）
                EsPageResult<DrugBasicInfoDo> esResult = elasticSearchService.fuzzyMultiSearchWithTotal(
                        ES_INDEX_NAME, Arrays.asList("drugName", "drugStdCode"), "*", 1, 1, DrugBasicInfoDo.class);
                if (!esResult.getRecords().isEmpty()) {
                    drugFromCache = esResult.getRecords().get(0);
                    // 写入缓存
                    redisService.setCacheObject(cacheKey, drugFromCache, CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES);
                    log.info("从ES获取任意药品数据并写入缓存成功");
                    return drugFromCache;
                }
            } catch (IOException e) {
                log.error("ES查询任意药品数据失败", e);
            }

            // ES未找到，查数据库
            List<DrugBasicInfoDo> drugList = listDrugBasicInfos();
            if (!drugList.isEmpty()) {
                drugFromCache = drugList.get(0);
                // 写入缓存
                redisService.setCacheObject(cacheKey, drugFromCache, CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES);
                log.info("从数据库获取任意药品数据并写入缓存成功");
                return drugFromCache;
            }
            return null;
        }

        // 场景2：按药品名称查询
        if (StringUtils.isNotEmpty(drug.getDrugName())) {
            String cacheKey = "drug:basic:info:name:" + drug.getDrugName();
            // 先查Redis缓存
            DrugBasicInfoDo drugFromCache = redisService.getCacheObject(cacheKey);
            if (drugFromCache != null) {
                log.info("从Redis缓存获取药品[{}]数据成功", drug.getDrugName());
                return drugFromCache;
            }

            // 缓存未命中，查Elasticsearch
            try {
                List<String> searchFields = Arrays.asList("drugName", "drugStdCode", "drugCode");
                EsPageResult<DrugBasicInfoDo> esResult = elasticSearchService.fuzzyMultiSearchWithTotal(
                        ES_INDEX_NAME, searchFields, drug.getDrugName(), 1, 1, DrugBasicInfoDo.class);
                if (!esResult.getRecords().isEmpty()) {
                    drugFromCache = esResult.getRecords().get(0);
                    // 写入缓存
                    redisService.setCacheObject(cacheKey, drugFromCache, CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES);
                    log.info("从ES获取药品[{}]数据并写入缓存成功", drug.getDrugName());
                    return drugFromCache;
                }
            } catch (IOException e) {
                log.error("ES查询药品[{}]数据失败", drug.getDrugName(), e);
            }

            // ES未找到，查数据库（按名称模糊查询第一条）
            DrugParm drugParm = new DrugParm();
            drugParm.setDrugName(drug.getDrugName());
            drugParm.setCurrentPage(1);
            drugParm.setPageSize(1);
            List<DrugBasicInfoDo> nameList = baseMapper.selectByName(drug.getDrugName()); // 假设此方法存在
            if (!ObjectUtils.isEmpty(nameList) && nameList.size() > 0) {
                drugFromCache = nameList.get(0);
                // 写入缓存
                redisService.setCacheObject(cacheKey, drugFromCache, CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES);
                log.info("从数据库获取药品[{}]数据并写入缓存成功", drug.getDrugName());
                return drugFromCache;
            }

            // 未找到，返回null或递归获取任意一条（根据业务需求选择）
            // return getOneDrugBasicInfo(null);  // 如需返回任意一条，取消注释此行
            return null;
        }

        // 场景3：按药品代码查询
        if (StringUtils.isNotEmpty(drug.getDrugCode())) {
            String cacheKey = "drug:basic:info:code:" + drug.getDrugCode();
            // 先查Redis哈希缓存（更高效的精确查询）
            RMap<String, DrugBasicInfoDo> drugHash = redissonClient.getMap(RS_HASH_KEY);
            DrugBasicInfoDo drugFromCache = drugHash.get(drug.getDrugCode());
            if (drugFromCache != null) {
                log.info("从Redis哈希缓存获取药品[代码:{}]数据成功", drug.getDrugCode());
                return drugFromCache;
            }

            // 缓存未命中，查Elasticsearch
            try {
                // 修正：使用termSearchWithTotal方法获取带总数的分页结果
                EsPageResult<DrugBasicInfoDo> esResult = elasticSearchService.termSearchWithTotal(
                        ES_INDEX_NAME, "drugCode", drug.getDrugCode(), 1, 1, DrugBasicInfoDo.class);
                if (!esResult.getRecords().isEmpty()) {
                    drugFromCache = esResult.getRecords().get(0);
                    // 写入Redis哈希缓存
                    drugHash.put(drug.getDrugCode(), drugFromCache);
                    drugHash.expire(CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES);
                    log.info("从ES获取药品[代码:{}]数据并写入缓存成功", drug.getDrugCode());
                    return drugFromCache;
                }
            } catch (IOException e) {
                log.error("ES查询药品[代码:{}]数据失败", drug.getDrugCode(), e);
            }

            // ES未找到，查数据库
            List<DrugBasicInfoDo> drugFromDbs = baseMapper.selectByCode(drug.getDrugCode());
            if (drugFromDbs != null) {
                // 写入Redis哈希缓存
                drugHash.put(drug.getDrugCode(), drugFromDbs.get(0));
                drugHash.expire(CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES);
                log.info("从数据库获取药品[代码:{}]数据并写入缓存成功", drug.getDrugCode());
                return drugFromDbs.get(0);
            }
            return null;
        }

        return null;
    }
    /**
     * 获取所有科室列表（带缓存逻辑）
     */
    @Override
    public List<DrugBasicInfoDo> listDrugBasicInfos() {
        RList<DrugBasicInfoDo> rList = redissonClient.getList(RS_INDEX_NAME);
        //  检查缓存是否存在且有数据
        if (rList.isExists()) {
            log.info("缓存 {} 命中，直接读取", RS_INDEX_NAME);
            return new ArrayList<>(rList);
        }
        // 缓存不存在，执行加载逻辑
        return loadDrugInfoCacheIfAbsent();
    }

    /**
     * 清除药品基础信息缓存
     */
    private void clearDeptCache() {
        try {
            redisService.deleteObject(RS_INDEX_NAME);
            log.debug("科室缓存 {} 已清除", RS_INDEX_NAME);
        } catch (Exception e) {
            log.warn("清除科室缓存失败", e);
        }
    }
    /**
     * 缓存不存在时加载缓存
     */
    private List<DrugBasicInfoDo> loadDrugInfoCacheIfAbsent() {
        RList<DrugBasicInfoDo> rList = redissonClient.getList(RS_INDEX_NAME);
        RLock cacheLock = redissonClient.getLock(CACHE_LOCK_PREFIX + RS_INDEX_NAME);
        boolean isCacheLocked = false;
        List<DrugBasicInfoDo> drugList = new ArrayList<>();

        try {
            log.info("缓存 {} 不存在，准备加载", RS_INDEX_NAME);
            // 预查数据库，减少锁持有时间
            drugList = queryDrugBasicInfoFromDb();

            // 尝试获取分布式锁
            isCacheLocked = cacheLock.tryLock(LOCK_WAIT_SECONDS, LOCK_HOLD_SECONDS, TimeUnit.SECONDS);
            if (!isCacheLocked) {
                log.warn("获取锁失败，等待其他线程加载缓存");
                // 等待缓存加载完成
                return waitForCacheLoad(rList);
            }

            // 双重检查缓存是否存在
            if (rList.isExists()) {
                log.info("双重检查发现缓存已存在，直接读取");
                return new ArrayList<>(rList);
            }

            // 写入缓存并设置过期时间
            log.info("获取锁成功，开始写入缓存");
            rList.addAll(drugList);
            rList.expire(CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
            return drugList;

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("缓存加载过程被中断", e);
            return drugList;
        } finally {
            // 确保锁释放
            releaseLock(cacheLock, isCacheLocked);
        }
    }

    /**
     * 数据库查询药品基础信息
     */
    private List<DrugBasicInfoDo> queryDrugBasicInfoFromDb() {
        return baseMapper.selectAll();
    }
    /**
     * 等待缓存加载完成
     */
    private List<DrugBasicInfoDo> waitForCacheLoad(RList<DrugBasicInfoDo> rList) throws InterruptedException {
        int waitCount = 0;
        while (waitCount < CACHE_WAIT_RETRIES && !rList.isExists()) {
            TimeUnit.MILLISECONDS.sleep(CACHE_WAIT_INTERVAL_MS);
            waitCount++;
        }
        return rList.isExists() ? new ArrayList<>(rList) : queryDrugBasicInfoFromDb();
    }
    /**
     * 安全释放锁
     */
    private void releaseLock(RLock lock, boolean isLocked) {
        if (isLocked && lock.isHeldByCurrentThread()) {
            try {
                lock.unlock();
                log.debug("锁 [{}] 已释放", lock.getName());
            } catch (IllegalMonitorStateException e) {
                log.warn("释放锁 [{}] 失败，可能已过期", lock.getName(), e);
            }
        }
    }
    /**
     * 生成索引的映射关系
     */
    private Map<String, Property> generateMap() {
        Map<String, Property> properties = new HashMap<>(6);
        // deptCode 字段 (精确匹配)
        properties.put("drugStdCode", Property.of(p -> p.keyword(k -> k)));
        // drugName 字段 (中文分词)
        properties.put("drugName", Property.of(p -> p.text(t -> t.analyzer("ik_max_word").searchAnalyzer("ik_smart"))));
        // drugCode 字段 (精确匹配)
        properties.put("drugCode", Property.of(p -> p.keyword(k -> k)));
        // drugSpec 字段 (精确匹配)
        properties.put("drugSpec", Property.of(p -> p.keyword(k -> k)));
        // drugUnit 字段 (精确匹配)
        properties.put("drugUnit", Property.of(p -> p.keyword(k -> k)));
        // firmId 字段 (中文分词)
        properties.put("firmId", Property.of(p -> p.text(t -> t.analyzer("ik_max_word").searchAnalyzer("ik_smart"))));
        return properties;
    }
    /**
     * 初始化索引和映射
     */
    public void initIndexAndMapping() throws IOException {
        try {
            // 检查索引是否存在
            if (elasticSearchService.existsIndex(ES_INDEX_NAME)) {
                log.debug("Elasticsearch索引 [{}] 已存在，无需初始化。", ES_INDEX_NAME);
                return;
            }

            log.info("Elasticsearch索引 [{}] 不存在，开始创建...", ES_INDEX_NAME);
            // 创建索引
            esClient.indices().create(CreateIndexRequest.of(c -> c.index(ES_INDEX_NAME)));
            log.info("索引 [{}] 创建成功。", ES_INDEX_NAME);

            // 短暂延时，确保索引就绪
            TimeUnit.MILLISECONDS.sleep(100);

            // 设置映射
            esClient.indices().putMapping(PutMappingRequest.of(p -> p
                    .index(ES_INDEX_NAME)
                    .properties(generateMap())
            ));
            log.info("Elasticsearch索引 [{}] 映射设置完成。", ES_INDEX_NAME);

        } catch (Exception e) {
            log.error("Elasticsearch索引初始化失败: {}", e.getMessage(), e);
            // 清理可能的残留索引
            try {
                if (elasticSearchService.existsIndex(ES_INDEX_NAME)) {
                    elasticSearchService.deleteIndex(ES_INDEX_NAME);
                    log.warn("已删除创建失败的索引 [{}]。", ES_INDEX_NAME);
                }
            } catch (Exception deleteEx) {
                log.error("清理失败索引时出错: {}", deleteEx.getMessage(), deleteEx);
            }
            throw new IOException("索引初始化失败，请检查ES服务和网络连接。", e);
        }
    }
    /**
     * 主动加载缓存
     */
    private void loadDrugCache(List<DrugBasicInfoDo> drugList) {
        RList<DrugBasicInfoDo> rList = redissonClient.getList(RS_INDEX_NAME);
        RLock cacheLock = redissonClient.getLock(CACHE_LOCK_PREFIX + RS_INDEX_NAME);
        boolean isCacheLocked = false;

        try {
            isCacheLocked = cacheLock.tryLock(LOCK_WAIT_SECONDS, LOCK_HOLD_SECONDS, TimeUnit.SECONDS);
            if (isCacheLocked) {
                rList.clear();
                rList.addAll(drugList);
                rList.expire(CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
                log.info("缓存 {} 重新加载完成，共 {} 条数据", RS_INDEX_NAME, drugList.size());
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("加载缓存时被中断", e);
        } finally {
            releaseLock(cacheLock, isCacheLocked);
        }
    }
    @Override
    public String checkInputDrugInfoValid(DrugBasicInfoDo drug){
        if (StringUtils.isEmpty(drug.getDrugStdCode())) {
            return "药品唯一码不得为空";
        }
        if (StringUtils.isEmpty(drug.getDrugSpec())){
            return "药品规格不得为空";
        }
        if (StringUtils.isEmpty(drug.getDrugCode())){
            return "药品通用名代码不得为空";
        }
        if (StringUtils.isEmpty(drug.getDrugName())){
            return "药品通用名不得为空";
        }
        if (StringUtils.isEmpty(drug.getFirmId())){
            return "药品厂家不得为空";
        }
        if (StringUtils.isEmpty(drug.getDoseUnit())){
            return "药品最小使用剂量单位不得为空";
        }
        if (StringUtils.isEmpty(drug.getMinPackUnit())){
            return "药品流通包装单位不得为空";
        }
        if (ObjectUtils.isEmpty(drug.getMinPack()) || drug.getMinPack() <= 0){
            return "药品流通包装不得为空或小于等于零";
        }
        if (ObjectUtils.isEmpty(drug.getDosePerUnit()) || drug.getDosePerUnit() <= 0.0){
            return "药品最小使用剂量不得为空或小于等于零";
        }
        return "";
    }
    @Override
    public boolean checkDrugUnique(DrugBasicInfoDo drug){

        // 1. 前提检查：如果 drug 对象为 null，直接返回 false。
        if (drug == null) {
            return false;
        }
        // 2. 定义条件A：检查第一组参数 (drugStdCode) 是否有效
        boolean isConditionAValid = StringUtils.isNotEmpty(drug.getDrugStdCode());
        // 3. 定义条件B：检查第二组参数 (drugCode, drugSpec, drugFirmId) 是否全部有效
        boolean isConditionBValid = StringUtils.isNotEmpty(drug.getDrugCode())
                && StringUtils.isNotEmpty(drug.getDrugSpec())
                && StringUtils.isNotEmpty(drug.getFirmId());
        // 4. 如果两组参数都无效，则无法进行唯一性校验，直接返回 false。
        if (!(isConditionAValid || isConditionBValid)){
            return false;
        }
        // 5. 初始化变量
        DrugBasicInfoDo drugFromDb = null;

        // 6. 优先使用 drugStdCode 查询
        if (isConditionAValid) {
            drugFromDb = baseMapper.selectByStd(drug.getDrugStdCode());
        }
        // 7. 如果通过 drugStdCode 没查到，再使用副主键组合 (drugCode, drugSpec, firmId) 查询
        if (isConditionBValid && ObjectUtils.isEmpty(drugFromDb)) {
            drugFromDb = baseMapper.selectBySubPrimaryKey(drug.getDrugCode(),drug.getDrugSpec(),drug.getFirmId());
        }
        // 8. 都没有查找到，直接返回 false。
        if (ObjectUtils.isEmpty(drugFromDb)) {
            return false;
        }

        // 如果数据库中找到了记录，需要进一步判断是不是自身
        // 使用唯一的业务主键 drug_std_code 进行比较
        // 因为 isConditionAValid 为 true 时才会进入更新，或者查询到的结果必然有 std code
        if ((StringUtils.isNotEmpty(drug.getDrugStdCode()) && drug.getDrugStdCode().equals(drugFromDb.getDrugStdCode())) ||
            (StringUtils.isNotEmpty(drug.getDrugCode()) && drug.getDrugCode().equals(drugFromDb.getDrugCode()) &&
             StringUtils.isNotEmpty(drug.getDrugSpec()) && drug.getDrugSpec().equals(drugFromDb.getDrugSpec()) &&
             StringUtils.isNotEmpty(drug.getFirmId()) && drug.getFirmId().equals(drugFromDb.getFirmId())
            )) {
            // --- 更新操作：找到了自身 ---
            // 这是合法的，不构成重复
            return true; // 返回 false，表示唯一
        } else {
            // --- 新增操作 或 更新时找到了其他重复记录 ---
            // 1. 新增：传入的 drug 没有 std code，但通过其他组合找到了 existing record。
            // 2. 更新：传入的 drug 有 std code，但找到的记录是另一个（拥有相同的 drugCode,Spec,Firm）。
            // 无论是哪种情况，都说明数据不唯一。
            return false; // 返回 true，表示不唯一
        }
    }
    /**
     * 同步单个药品数据到Redis缓存（支持新增和更新）
     * 此方法必须在数据库事务成功提交后调用。
     *
     * @param drug 科室对象
     */
    private void syncDrugToRedis(DrugBasicInfoDo drug) {
        if (drug == null || StringUtils.isEmpty(drug.getDrugStdCode())) {
            log.error("药品StdCode不能为空，同步Redis失败");
            return;
        }

        // 使用科室ID作为锁键，粒度更细，并发性能更好
        RLock cacheLock = redissonClient.getLock(CACHE_LOCK_PREFIX + drug.getDrugStdCode());
        boolean isLocked = false;

        try {
            // 尝试获取锁
            isLocked = cacheLock.tryLock(LOCK_WAIT_SECONDS, LOCK_HOLD_SECONDS, TimeUnit.SECONDS);
            if (!isLocked) {
                log.warn("获取缓存锁失败，药品 [StdCode: {}] 的Redis同步可能延迟，将由下次查询触发更新。", drug.getDrugStdCode());
                return;
            }

            // 1. 更新哈希缓存 (RMap) - 高效，原子性覆盖
            RMap<String, DrugBasicInfoDo> drugHash = redissonClient.getMap(RS_HASH_KEY);
            drugHash.put(drug.getDrugStdCode(), drug);
            drugHash.expire(CACHE_EXPIRE_HOURS, TimeUnit.HOURS);

            // 2. 更新列表缓存 (RList) - 处理新增和更新
            RList<DrugBasicInfoDo> drugList = redissonClient.getList(RS_INDEX_NAME);
            if (drugList.isExists()) {
                boolean replaced = false;
                for (int i = 0; i < drugList.size(); i++) {
                    DrugBasicInfoDo existingDrug = drugList.get(i);
                    if (drug.getDrugStdCode().equals(existingDrug.getDrugStdCode())) {
                        drugList.set(i, drug); // 更新
                        replaced = true;
                        break;
                    }
                }
                if (!replaced) {
                    drugList.add(drug); // 新增
                }
                drugList.expire(CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
            } else {
                // 如果列表缓存不存在，说明缓存可能已过期或被清除。
                // 此时最安全的做法是不操作列表，让下一次查询来触发完整的缓存加载。
                log.info("列表缓存 {} 不存在，仅更新哈希缓存。", RS_INDEX_NAME);
            }

            log.info("药品 [StdCode: {}] 已成功同步到Redis缓存", drug.getDrugStdCode());

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("同步Redis缓存时被中断", e);
        } finally {
            releaseLock(cacheLock, isLocked);
        }
    }
    /**
     * 从Redis缓存中移除单个科室数据
     * @param id 科室ID
     */
    private void removeDrugFromRedis(String id) {
        if (StringUtils.isEmpty(id)) {
            return;
        }

        RLock cacheLock = redissonClient.getLock(CACHE_LOCK_PREFIX + id);
        boolean isLocked = false;

        try {
            isLocked = cacheLock.tryLock(LOCK_WAIT_SECONDS, LOCK_HOLD_SECONDS, TimeUnit.SECONDS);
            if (isLocked) {
                // 1. 从哈希中删除
                RMap<String, DrugBasicInfoDo> drugHash = redissonClient.getMap(RS_HASH_KEY);
                drugHash.remove(id);

                // 2. 从列表中删除
                RList<DrugBasicInfoDo> drugList = redissonClient.getList(RS_INDEX_NAME);
                if (drugList.isExists()) {
                    // 先获取所有元素，找到匹配的ID
                    List<DrugBasicInfoDo> allDrugs = new ArrayList<>(drugList);
                    boolean removed = false;

                    for (DrugBasicInfoDo drug : allDrugs) {
                        if (id.equals(drug.getDrugStdCode())) {
                            // 使用Redisson的remove方法删除对象
                            drugList.remove(drug);
                            removed = true;
                            break;
                        }
                    }
                    if (!removed) {
                        log.debug("StdCode为 [{}] 的药品不在Redis列表缓存中", id);
                    }
                }

                log.info("药品 [StdCode: {}] 已从Redis缓存中移除", id);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("从Redis缓存移除药品时被中断", e);
        } finally {
            releaseLock(cacheLock, isLocked);
        }
    }

}
